#import "CocoaLink.h"
#import <MathematicaKit/MathematicaKit.h>
#import "CocoaClass.h"
#import "CocoaType.h"
#import "CocoaGlobals.h"
#import "CocoaDelegate.h"
#import <objc/objc-runtime.h>
#import "mathlink.h"

MMathLink* GetLink()
{
	initGlobals();
	
	MEnvironment* env = [[[MEnvironment alloc] 
		initWithEnvironment: stdenv close: NO] autorelease];
	
	return [[[MMathLink alloc] initWithEnvironment: env link: stdlink close: NO] autorelease];
}

NSMutableArray* GetFunctionParts(MExpr* head)
{
	NSMutableArray* parts = [[[NSMutableArray alloc] initWithCapacity: 0] autorelease];
	[parts addObject: head];
	return parts;
}

MExpr* MessageExpr(NSString* symbol, NSString* tag, NSString* param1, NSString* param2)
{
	MExpr* messageName = MFunction(MSymbol(@"MessageName"), MSymbol(symbol), MString(tag), nil);
	MExpr* message = nil;
	
	if(param1 && param2)
		message = MFunction(MSymbol(@"Message"), messageName, MString(param1), MString(param2), nil);
	else if(param1)
		message = MFunction(MSymbol(@"Message"), messageName, MString(param1), nil);
	else
		message = MFunction(MSymbol(@"Message"), messageName, nil);
	return message;
}

MExpr* Message(MMathLink* link, NSString* symbol, NSString* tag, NSString* param1, NSString* param2, BOOL fail)
{
	MExpr* message = MessageExpr(symbol, tag, param1, param2);
	[MFunction(MSymbol(@"EvaluatePacket"), message, nil) put: link];
	[link nextPacket];
	
	if(fail)
		[MSymbol(@"$Failed") put: link];
	return MSymbol(@"$Failed");
}

typedef MExpr* (*CLEvaluateFunc)(MMathLink* link, MExpr* e);

MExpr* CocoaEvaluateFunction(MMathLink* link, MExpr* list, CLEvaluateFunc func)
{
	int argc;
	if(![list checkFunction: kSymbolList length: &argc])
		return Message(link, @"CocoaObject", @"argx", [list description], nil, NO);
	
	if(argc == 1)
		return (*func)(link, [list functionPart: 1]);
	else
	{
		NSMutableArray* parts = [NSMutableArray arrayWithCapacity: argc + 1];
		int i;
		
		[parts addObject: kSymbolList];
		for(i = 1; i <= argc; i++)
			[parts addObject: (*func)(link, [list functionPart: i])];
		return [MExpr exprWithFunctionParts: parts];
	}
}

void CocoaCallPacket(CLEvaluateFunc func)
{
	NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
	MMathLink* link = GetLink();
	MExpr* e = CocoaEvaluateFunction(link, [MExpr exprWithLink: link], func);
	[e put: link];
	[pool release];
}

@interface CocoaMessageHandler : NSObject
{
}
- (void) handleMessage: (int) message link: (MMathLink*) link;
@end

@implementation CocoaMessageHandler

- (void) handleMessage: (int) message link: (MMathLink*) link
{
	switch(message)
	{
		case MLInterruptMessage:
		case MLAbortMessage:
			[[NSApplication sharedApplication] stop: nil];
			break;
	}
}

@end

int CocoaRun(void)
{
#if 1
	NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
	MMathLink* link = GetLink();
	[link setDelegate: [[[CocoaMessageHandler alloc] init] autorelease]];
	[link setRunLoop: CFRunLoopGetCurrent()];
	
	NS_DURING
		[[NSApplication sharedApplication] activateIgnoringOtherApps: YES];
		[[NSApplication sharedApplication] run];
	NS_HANDLER
	NS_ENDHANDLER
	
	[link setRunLoop: NULL];
	[link setDelegate: nil];
	[pool release];
	return 0;
#elif 0
	return NSApplicationMain(gArgc, gArgv);
#else
	NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
	CFRunLoopRef loop = CFRunLoopGetCurrent();
	CFRunLoopRun();
	[pool release];
	return 0;
#endif
}

MExpr* CocoaClassFunc(MMathLink* link, MExpr* objExpr)
{
	NSString* name = [objExpr string];
	CocoaClass* klass = [[[CocoaClass alloc] initWithName: name] autorelease];
	if(!klass)
		return Message(link, @"CocoaClass", @"inval", name, nil, NO);
	return [klass uniqueExpr];
}

void cocoaClass(void)
{
	CocoaCallPacket(CocoaClassFunc);
}

void CocoaClasses(void)
{
	NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
	MMathLink* link = GetLink();
	NSMutableArray* parts = GetFunctionParts(kSymbolList);
	int numClasses = objc_getClassList(NULL, 0);
	Class* classes = (Class*) malloc(sizeof(Class) * numClasses);
	
	if(objc_getClassList(classes, numClasses) == numClasses)
	{
		int i;
		for(i = 0; i < numClasses; i++)
		{
			if(classes[i] != [MEnvironment class]
				&& classes[i] != [MEvaluation class]
				&& classes[i] != [MEvaluator class]
				&& classes[i] != [MExpr class]
				&& classes[i] != [MMathLink class]
				&& classes[i] != [MMathLinkMark class]
				&& classes[i] != [MPacket class]
				&& classes[i] != [CocoaClass class]
				&& classes[i] != [CocoaType class])
			{
				MExpr* name = MString(NSStringFromClass(classes[i]));
				[parts addObject: name];
			}
		}
	}
	free(classes);
	[[MExpr exprWithFunctionParts: parts] put: link];
	[pool release];
}

void cocoaObjects(void)
{
	NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
	MMathLink* link = GetLink();
	NSMutableArray* parts = GetFunctionParts(kSymbolList);
	NSEnumerator* e = [GetSymbols() keyEnumerator];
	id key;

	while((key = [e nextObject]) != nil)
		[parts addObject: key];
	
	[[MExpr exprWithFunctionParts: parts] put: link];
	[pool release];
}

MExpr* CocoaObjectMethodsFunc(MMathLink* link, MExpr* objExpr)
{
	NSMutableArray* parts;
	Class klass;
	BOOL isKlass;
	struct objc_method_list* mlist;
	void* iter = 0;
	
	id obj = [objExpr cocoaObject];
	if(!obj)
		return Message(link, @"CocoaObject", @"inval", [objExpr description], nil, NO);
	
	parts = GetFunctionParts(kSymbolList);
	isKlass = [obj isKindOfClass: [CocoaClass class]];
	klass = (isKlass ? [obj klass]->isa : [obj class]);
	
	do {
		while((mlist = class_nextMethodList(klass, &iter)) != NULL)
		{
			int i;
			for(i = 0; i < mlist->method_count; i++)
				[parts addObject: MString(NSStringFromSelector(mlist->method_list[i].method_name))];
		}
	} while(!isKlass && ((klass = klass->super_class) != nil));
	return [MExpr exprWithFunctionParts: parts];
}

void CocoaObjectMethods(void)
{
	CocoaCallPacket(CocoaObjectMethodsFunc);
}

BOOL CocoaSendMessage(MMathLink* link, id obj, MExpr* objExpr, MExpr* selExpr, MExpr* argExpr)
{
	MExpr* resultExpr = nil;
	NSInvocation* invocation;
	int i, realArgc, argc;
	NSMethodSignature* sig;
	void* result;
	
	SEL sel = NSSelectorFromString([selExpr string]);
	
	// make sure a valid object was specified
	if(!obj)
	{
		Message(link, @"CocoaObject", @"inval", [objExpr description], nil, YES);
		return NO;
	}
	// make sure a valid selector was specified
	if(!sel)
	{
		Message(link, @"CocoaSelector", @"inval", [selExpr description], nil, YES);
		return NO;
	}
	// make sure the user can't affect our memory management
	if(	sel == @selector(new) ||
		sel == @selector(alloc) ||
		sel == @selector(allocWithZone:) ||
		sel == @selector(copy) ||
		sel == @selector(copyWithZone:) ||
		sel == @selector(mutableCopyWithZone:) ||
		sel == @selector(retain) || 
		sel == @selector(release) || 
		sel == @selector(autorelease) ||
		sel == @selector(zone))
	{
		Message(link, @"CocoaObject", @"mem", [selExpr description], nil, YES);
		return NO;
	}
	// make sure an argument list was specified
	if(![argExpr checkFunction: kSymbolList length: &argc])
	{
		Message(link, @"CocoaObject", @"argx", [argExpr description], nil, YES);
		return NO;
	}
	// find the method we're going to call
	if([obj isKindOfClass: [CocoaClass class]])
		obj = [obj klass];
	// get the method signature object
	sig = [obj methodSignatureForSelector: sel];
	// make sure the method exists for this object
	if(!sig)
	{
		Message(link, @"CocoaObject", @"sel", [objExpr description], [selExpr description], YES);
		return NO;
	}
	// create the invocation
	invocation = [NSInvocation invocationWithMethodSignature: sig];
	[invocation setSelector: sel];
	
	realArgc = [sig numberOfArguments];
	for(i = 0; i < realArgc; i++)
	{
		CocoaType* type = [CocoaType typeWithType: [sig getArgumentTypeAtIndex: i]];
		MExpr* arg;
		NSValue* value;
		void *data;
		
		// the first argument is always the object
		if(i == 0)
			arg = objExpr;
		// the second argument is the selector
		else if(i == 1)
			arg = selExpr;
		// all remaining arguments are passed to the method
		else if((i - 2 + 1) <= argc)
			arg = [argExpr functionPart: i - 2 + 1];
		else
			arg = MSymbol(@"Null");
		
		value = [type valueForExpr: arg];
		if(!value)
		{
			Message(link, @"CocoaObject", @"conv", [arg description], [type description], YES);
			return NO;
			//return [self putError: [NSString stringWithFormat: 
			//	@"Could not convert %@ to type %@", arg, type] onLink: link];
		}
		data = malloc([type byteCount]);
		[value getValue: data];
		[invocation setArgument: data atIndex: i];
		free(data);
	}
	
	result = malloc([sig methodReturnLength]);
		
	// send the message and get its return value
	NS_DURING
		[invocation invokeWithTarget: obj];
		if([sig methodReturnLength])
		{
			[invocation getReturnValue: result];
			obj = [NSValue value: result withObjCType: [sig methodReturnType]];
			resultExpr = [obj uniqueExpr];
		}
		else
			resultExpr = kSymbolNull;
		free(result);
	NS_HANDLER
		free(result);
		Message(link, @"CocoaObject", @"expt", [localException name], [localException reason], YES);
		return NO;
	NS_ENDHANDLER
	
	return [resultExpr put: link];
}

void CocoaSend(void)
{
	NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
	MMathLink* link = GetLink();
	MExpr* list = [MExpr exprWithLink: link];
	int argc;
	MExpr *objExpr, *selExpr, *argExpr;
	
	if(![list checkFunction: kSymbolList length: &argc] || argc != 3)
	{
		Message(link, @"CocoaObject", @"argx", [list description], nil, YES);
		[pool release];
		return;
	}
	
	objExpr = [list functionPart: 1];
	selExpr = [list functionPart: 2];
	argExpr = [list functionPart: 3];
	CocoaSendMessage(link, [objExpr cocoaObject], objExpr, selExpr, argExpr);
	[pool release];
}

void CocoaAlloc(void)
{
	NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
	MMathLink* link = GetLink();
	MExpr* list = [MExpr exprWithLink: link];
	int argc;
	MExpr *clsExpr, *selExpr, *argExpr, *objExpr = nil;
	CocoaClass* klass = nil;
	id obj;
	
	if(![list checkFunction: kSymbolList length: &argc] || argc != 3)
	{
		Message(link, @"CocoaObject", @"argx", [list description], nil, YES);
		[pool release];
		return;
	}
	
	clsExpr = [list functionPart: 1];
	selExpr = [list functionPart: 2];
	argExpr = [list functionPart: 3];
		
	if([clsExpr checkHead: kSymbolString] || [clsExpr checkHead: kSymbolSymbol])
		klass = [[[CocoaClass alloc] initWithName: [clsExpr string]] autorelease];
	else
		klass = [clsExpr cocoaObject];
		
	if(!klass)
	{
		Message(link, @"CocoaClass", @"inval", [clsExpr description], nil, YES);
		[pool release];
		return;
	}
		
	// not all classes can be allocated directly
	NS_DURING
		obj = [[klass klass] alloc];
	NS_HANDLER
		Message(link, @"CocoaClass", @"alloc", [clsExpr description], nil, YES);
		return;
	NS_ENDHANDLER
		
	objExpr = [obj expr];
	if(CocoaSendMessage(link, obj, objExpr, selExpr, argExpr))
		[obj release];
	else
	{
		[GetSymbols() removeObjectForKey: objExpr];
		[obj dealloc];
	}
	[pool release];
}

MExpr* CocoaObjectQFunc(MMathLink* link, MExpr* objExpr)
{
	if([GetSymbols() objectForKey: objExpr] != nil)
		return kSymbolTrue;
	return kSymbolFalse;
}

void CocoaObjectQ(void)
{
	CocoaCallPacket(CocoaObjectQFunc);
}

MExpr* CocoaObjectReleaseFunc(MMathLink* link, MExpr* objExpr)
{
	[GetSymbols() removeObjectForKey: objExpr];
	return kSymbolNull;
}

void CocoaObjectRelease(void)
{
	CocoaCallPacket(CocoaObjectReleaseFunc);
}

MExpr* CocoaObjectRefCountFunc(MMathLink* link, MExpr* objExpr)
{
	id obj = [objExpr cocoaObject];
	if(!obj)
		return Message(link, @"CocoaObject", @"inval", [objExpr description], nil, NO);
	
	return MInteger([obj retainCount]);
}

void CocoaObjectRefCount(void)
{
	CocoaCallPacket(CocoaObjectRefCountFunc);
}

MExpr* CocoaObjectClassFunc(MMathLink* link, MExpr* objExpr)
{
	id obj = [objExpr cocoaObject];
	if(!obj)
		return Message(link, @"CocoaObject", @"inval", [objExpr description], nil, NO);
		
	if([obj isKindOfClass: [CocoaClass class]])
		return MString(NSStringFromClass([obj klass]));
	else
		return MString(NSStringFromClass([obj class]));
}

void cocoaObjectClass(void)
{
	CocoaCallPacket(CocoaObjectClassFunc);
}

MExpr* CocoaObjectSuperclassFunc(MMathLink* link, MExpr* objExpr)
{
	Class klass;
	id obj = [objExpr cocoaObject];
	if(!obj)
		return Message(link, @"CocoaObject", @"inval", [objExpr description], nil, NO);
	
	if([obj isKindOfClass: [CocoaClass class]])
		klass = [[obj klass] superclass];
	else
		klass = [obj superclass];
	
	if(klass)
		return MString(NSStringFromClass(klass));
	else
		return kSymbolNull;
}

void cocoaObjectSuperclass(void)
{
	CocoaCallPacket(CocoaObjectSuperclassFunc);
}

MExpr* CocoaObjectClassesFunc(MMathLink* link, MExpr* objExpr)
{
	Class klass;
	id obj = [objExpr cocoaObject];
	if(!obj)
		return Message(link, @"CocoaObject", @"inval", [objExpr description], nil, NO);
	
	if([obj isKindOfClass: [CocoaClass class]])
		klass = [obj klass];
	else
		klass = [obj class];
	
	NSMutableArray* parts = GetFunctionParts(kSymbolList);
	while(klass != nil)
	{
		[parts addObject: MString(NSStringFromClass(klass))];
		klass = [klass superclass];
	}
	return [MExpr exprWithFunctionParts: parts];
}

void CocoaObjectClasses(void)
{
	CocoaCallPacket(CocoaObjectClassesFunc);
}

MExpr* CocoaObjectDescriptionFunc(MMathLink* link, MExpr* objExpr)
{
	id obj = [objExpr cocoaObject];
	if(!obj)
		return Message(link, @"CocoaObject", @"inval", [objExpr description], nil, NO);
	
	NS_DURING
		NS_VALUERETURN(MString([obj description]), MExpr*);
	NS_HANDLER
		return MSymbol(@"$Failed");
	NS_ENDHANDLER
}

void CocoaObjectDescription(void)
{
	CocoaCallPacket(CocoaObjectDescriptionFunc);
}

MExpr* CocoaToExpressionFunc(MMathLink* link, MExpr* objExpr)
{
	id obj = [objExpr cocoaObject];
	if(!obj)
		return Message(link, @"CocoaObject", @"inval", [objExpr description], nil, NO);
	return [obj toExpression];
}

void cocoaToExpression(void)
{
	CocoaCallPacket(CocoaToExpressionFunc);
}

void cocoaDelegate(void)
{
	NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
	MMathLink* link = GetLink();
	MExpr* args = [MExpr exprWithLink: link];
	
	int len, i;
	if([args checkFunction: kSymbolList length: &len] && (len == 2))
	{
		MExpr* context = [args functionPart: 1];
		MExpr* methods = [args functionPart: 2];
		
		NSMutableDictionary* methodSigs = [NSMutableDictionary dictionary];
		NSMutableDictionary* methodImpls = [NSMutableDictionary dictionary];
		
		if([methods checkFunction: kSymbolList length: &len])
		{
			for(i = 1; i <= len; i++)
			{
				MExpr* meth = [methods functionPart: i];
				int methLen;
				if([meth checkFunction: kSymbolList length: &methLen] && (methLen == 3))
				{
					NSString* name = [[meth functionPart: 1] string];
					id sig = [[meth functionPart: 2] cocoaObject];
					MExpr* func = [meth functionPart: 3];
					
					if(name && sig && func)
					{
						[methodSigs setValue: sig forKey: name];
						[methodImpls setValue: func forKey: name];
					}
				}
			}
		}
		
		id delegate = [[CocoaDelegate alloc] initWithContext: context
			signatures: methodSigs implementations: methodImpls];
		[[delegate uniqueExpr] put: link];
		[delegate release];
	}
	else
		[MSymbol(@"$Failed") put: link];
	[pool release];
}

void CocoaData(const unsigned char* theData, int theLength)
{
	NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
	NSData* data = [NSData dataWithBytes: theData length: theLength];
	[[data uniqueExpr] put: GetLink()];
	[pool release];
}
